#pragma once

#include <utility>
#include "SSLCommon.h"
#include "PointerVector.h"
#include "Asn1Codec.h"

/**
 * @file
 * See detailed explanation of the TLS/SSL protocol support in PcapPlusPlus in SSLLayer.h
 */

/**
 * \namespace pcpp
 * \brief The main namespace for the PcapPlusPlus lib
 */
namespace pcpp
{

	/**
	 * @class SSLCipherSuite
	 * Represents a cipher-suite and enables access all information about it such as all algorithms it encapsulates,
	 * its ID (as appears in the client-hello or server-hello messages),
	 * its name (e.g "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA") etc. PcapPlusPlus contains static instances of this type
	 * for all known cipher-suites and enables access to them through name or ID (see getCipherSuiteByID() and
	 * getCipherSuiteByName() ). List of cipher-suite was taken from here:
	 * http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
	 */
	class SSLCipherSuite
	{
	public:
		/**
		 * A c'tor for this class, should never be used by a user
		 * @param[in] id Cipher-suite ID
		 * @param[in] keyExAlg Key-exchange algorithm used in this cipher-suite
		 * @param[in] authAlg Authentication algorithm used in this cipher-suite
		 * @param[in] symKeyAlg Symmetric key algorithm used in this cipher-suite
		 * @param[in] MACAlg MAC algorithm used in this cipher-suite
		 * @param[in] name String representation of this cipher-suite
		 */
		SSLCipherSuite(uint16_t id, SSLKeyExchangeAlgorithm keyExAlg, SSLAuthenticationAlgorithm authAlg,
		               SSLSymetricEncryptionAlgorithm symKeyAlg, SSLHashingAlgorithm MACAlg, const char* name)
		    : m_Id(id), m_KeyExAlg(keyExAlg), m_AuthAlg(authAlg), m_SymKeyAlg(symKeyAlg), m_MACAlg(MACAlg), m_Name(name)
		{}

		/**
		 * @return Cipher-suite ID
		 */
		uint16_t getID() const
		{
			return m_Id;
		}

		/**
		 * @return String representation of this cipher-suite
		 */
		std::string asString() const
		{
			return m_Name;
		}

		/**
		 * @return Key-exchange algorithm used in this cipher-suite
		 */
		SSLKeyExchangeAlgorithm getKeyExchangeAlg() const
		{
			return m_KeyExAlg;
		}

		/**
		 * @return Authentication algorithm used in this cipher-suite
		 */
		SSLAuthenticationAlgorithm getAuthAlg() const
		{
			return m_AuthAlg;
		}

		/**
		 * @return Symmetric key algorithm used in this cipher-suite
		 */
		SSLSymetricEncryptionAlgorithm getSymKeyAlg() const
		{
			return m_SymKeyAlg;
		}

		/**
		 * @return MAC algorithm used in this cipher-suite
		 */
		SSLHashingAlgorithm getMACAlg() const
		{
			return m_MACAlg;
		}

		/**
		 * A static method that returns a cipher-suite instance by ID
		 * @param[in] id Cipher-suite ID
		 * @return A cipher-suite instance matching this ID or nullptr if ID not found
		 */
		static SSLCipherSuite* getCipherSuiteByID(uint16_t id);

		/**
		 *  A static method that returns a cipher-suite instance by name
		 *  @param[in] name Cipher-suite name (e.g "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA")
		 *  @return A cipher-suite instance matching this name or nullptr if name not found
		 */
		static SSLCipherSuite* getCipherSuiteByName(std::string name);

	private:
		uint16_t m_Id;
		SSLKeyExchangeAlgorithm m_KeyExAlg;
		SSLAuthenticationAlgorithm m_AuthAlg;
		SSLSymetricEncryptionAlgorithm m_SymKeyAlg;
		SSLHashingAlgorithm m_MACAlg;
		std::string m_Name;
	};

	/**
	 * @class SSLExtension
	 * Represents a SSL/TLS extension. This is a base class that can represent any type of extension. Inherited classes
	 * may contain parsing logic for specific extensions. This class provides capabilities such as getting the extension
	 * type, length and viewing the extension data as raw (byte array)
	 */
	class SSLExtension
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data for the extension
		 */
		explicit SSLExtension(uint8_t* data);

		virtual ~SSLExtension() = default;

		/**
		 * @return The type of the extension as enum
		 */
		SSLExtensionType getType() const;

		/**
		 * @return The type of the extension as a numeric value
		 */
		uint16_t getTypeAsInt() const;

		/**
		 * @return The length of the extension data in bytes (not including the type and length fields)
		 */
		uint16_t getLength() const;

		/**
		 * @return The total length of the extension, including type and length fields and the extension data field
		 */
		uint16_t getTotalLength() const;

		/**
		 * @return A pointer to the raw data of the extension
		 */
		uint8_t* getData() const;

	protected:
		/**
		 * @struct SSLExtensionStruct
		 * Represents the common fields of the extension
		 */
		struct SSLExtensionStruct
		{
			/** Extension type */
			uint16_t extensionType;
			/** Extension length */
			uint16_t extensionDataLength;
			/** Extension data as raw (byte array) */
			uint8_t extensionData[];
		};

		uint8_t* m_RawData;

		SSLExtensionStruct* getExtensionStruct() const
		{
			return reinterpret_cast<SSLExtensionStruct*>(m_RawData);
		}
	};

	/**
	 * @class SSLServerNameIndicationExtension
	 * Represents SSL/TLS Server Name Indication extension. Inherits from SSLExtension and add parsing of the hostname
	 * written in the extension data
	 */
	class SSLServerNameIndicationExtension : public SSLExtension
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data for the extension
		 */
		explicit SSLServerNameIndicationExtension(uint8_t* data) : SSLExtension(data)
		{}

		/**
		 * @return The hostname written in the extension data
		 */
		std::string getHostName() const;
	};

	/**
	 * @class SSLSupportedVersionsExtension
	 * Represents TLS Supported Versions extension. Inherits from SSLExtension and adds parsing of the list
	 * of supported versions mentioned in the extension data
	 */
	class SSLSupportedVersionsExtension : public SSLExtension
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data for the extension
		 */
		explicit SSLSupportedVersionsExtension(uint8_t* data) : SSLExtension(data)
		{}

		/**
		 * @return The list of supported versions mentioned in the extension data
		 */
		std::vector<SSLVersion> getSupportedVersions() const;
	};

	/**
	 * @class TLSSupportedGroupsExtension
	 * Represents TLS Supported Groups extension. Inherits from SSLExtension and adds parsing of the
	 * supported groups (Elliptic Curves) mentioned in the extension data
	 */
	class TLSSupportedGroupsExtension : public SSLExtension
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data for the extension
		 */
		explicit TLSSupportedGroupsExtension(uint8_t* data) : SSLExtension(data)
		{}

		/**
		 * @return A vector of the supported groups (also known as "Elliptic Curves")
		 */
		std::vector<uint16_t> getSupportedGroups() const;
	};

	/**
	 * @class TLSECPointFormatExtension
	 * Represents TLS EC (Elliptic Curves) Point Format extension. Inherits from SSLExtension and adds parsing of the
	 * EC point formats mentioned in the extension data
	 */
	class TLSECPointFormatExtension : public SSLExtension
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data for the extension
		 */
		explicit TLSECPointFormatExtension(uint8_t* data) : SSLExtension(data)
		{}

		/**
		 * @return A vector of the elliptic curves point formats
		 */
		std::vector<uint8_t> getECPointFormatList() const;
	};

	/**
	 * @class SSLx509Certificate
	 * Represents a x509v3 certificate. the SSLCertificateMessage class returns an instance of this class as the
	 * certificate. Currently this class doesn't do much as it doesn't parse the certificate. It only acts as container
	 * to the raw data and returns general info as data as raw, length, etc. In the future I may add full parsing of the
	 * certificate
	 */
	class SSLx509Certificate
	{
	public:
		/**
		 * C'tor for this class
		 * @param[in] data The raw data of the certificate
		 * @param[in] dataLen The length in bytes of the raw data
		 * @param[in] allDataExists Certificate messages usually spread on more than 1 packet. So a certificate is
		 * likely to split between 2 packets or more. This field indicates whether the raw data contains all
		 * certificate data of just a part of it
		 */
		SSLx509Certificate(uint8_t* data, size_t dataLen, bool allDataExists)
		    : m_Data(data), m_DataLen(dataLen), m_AllDataExists(allDataExists)
		{}

		/**
		 * @return A pointer to the raw data
		 */
		uint8_t* getData() const
		{
			return m_Data;
		}

		/**
		 * @return Raw data length
		 */
		size_t getDataLength() const
		{
			return m_DataLen;
		}

		/**
		 * @return The root ASN.1 record of the certificate data. All of the certificate data will be under this record.
		 * If the Root ASN.1 record is malformed, an exception is thrown
		 */
		Asn1SequenceRecord* getRootAsn1Record();

		/**
		 * Certificate messages usually spread on more than 1 packet. So a certificate is likely to split between 2
		 * packets or more. This method provides an indication whether all certificate data exists or only part of it
		 * @return True if this data contains all certificate data, false otherwise
		 */
		bool allDataExists() const
		{
			return m_AllDataExists;
		}

	private:
		std::unique_ptr<Asn1Record> m_Asn1Record;
		uint8_t* m_Data;
		size_t m_DataLen;
		bool m_AllDataExists;
	};

	class SSLHandshakeLayer;

	/**
	 * @class SSLHandshakeMessage
	 * A base class for SSL/TLS handshake messages. This is an abstract class and cannot be instantiated. SSL/TLS
	 * handshake messages are contained in SSLHandshakeLayer, meaning a SSLHandshakeLayer instance can contain one or
	 * more SSLHandshakeMessage instances. For example: one SSLHandshakeLayer may contain a server-hello, certificate,
	 * server-key-exchange, and server-hello-done messages (although it's not such a common case, most handshake layers
	 * contain 1 handshake message only)
	 */
	class SSLHandshakeMessage
	{
	public:
		virtual ~SSLHandshakeMessage() = default;

		/**
		 * A factory method for creating instances of handshake messages from raw data
		 * @param[in] data The raw data containing 1 handshake message
		 * @param[in] dataLen Raw data length in bytes
		 * @param[in] container A pointer to the SSLHandshakeLayer instance which will contain the created message.
		 * This parameter is required because the handshake message includes a pointer to its container
		 */
		static SSLHandshakeMessage* createHandshakeMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		/**
		 * @return The handshake message type
		 */
		virtual SSLHandshakeType getHandshakeType() const;

		/**
		 * @return The handshake message length in bytes. Notice that sometimes the handshake message is divided between
		 * several packets, in this case this method will return the length of part of the message in the current packet
		 */
		virtual size_t getMessageLength() const;

		/**
		 * @return True if current packet contains the entire message or false otherwise. This method is important
		 * because sometimes handshake messages are divided in consequent packets (happens a lot in certificate messages
		 * which usually contain several KB of data which is larger than standard packet size, so the message is divided
		 * between several packets)
		 */
		virtual bool isMessageComplete() const;

		/**
		 * @return A pointer to the SSLHandshakeLayer instance containing this message
		 */
		SSLHandshakeLayer* getContainingLayer() const
		{
			return m_Container;
		}

		/**
		 * @return A string representation of the message type (e.g "Client Hello message")
		 */
		virtual std::string toString() const = 0;

	protected:
		SSLHandshakeMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		uint8_t* m_Data;
		size_t m_DataLen;
		SSLHandshakeLayer* m_Container;
	};

	/**
	 * @class SSLClientHelloMessage
	 * Represents a client-hello message (type 1). Inherits from SSLHandshakeMessage and adds parsing of all fields
	 * of this message including the message extensions, cipher-suite list, etc.
	 */
	class SSLClientHelloMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * @struct ClientHelloTLSFingerprint
		 * A struct that contains all the elements needed for creating a Client Hello TLS fingerprinting:
		 * TLS version, a list of Cipher Suite IDs, a list of Extensions IDs, a list of support groups and a list of
		 * EC point formats.
		 * You can read more about this in SSLClientHelloMessage#generateTLSFingerprint().
		 * This struct contains methods to extract the TLS fingerprint itself: toString() and toMD5()
		 */
		struct ClientHelloTLSFingerprint
		{
			/** TLS version */
			uint16_t tlsVersion;
			/** A list of Cipher Suite IDs */
			std::vector<uint16_t> cipherSuites;
			/** A list of extension IDs */
			std::vector<uint16_t> extensions;
			/** A list of Suppotred Groups taken from the "supported groups" TLS extension (if exist in the message) */
			std::vector<uint16_t> supportedGroups;
			/** A list of EC point formats taken from the "EC point formats" TLS extension (if exist in the message) */
			std::vector<uint8_t> ecPointFormats;

			/**
			 * @return A string representing the TLS fingerprint, for example:
			 * <b>771,4866-4867-4865-255,0-11-10-35-22-23-13-43-45-51,29-23-30-25-24,0-1-2</b>
			 *
			 * This string has the following format:
			 * <b>TLSVersion,CipherSuiteIDs,ExtensionIDs,SupportedGroups,ECPointFormats</b>
			 *
			 * The extension IDs, supported groups and EC point formats are each separated by a "-".
			 * If the message doesn't include the "supported groups" or "EC point formats" extensions, they will be
			 * replaced by an empty string for example: <b>771,4866-4867-4865-255,0-11-10-35-22-23-13-43-45-51,,</b>
			 */
			std::string toString();

			/**
			 * @return An MD5 hash of the string generated by toString()
			 */
			std::string toMD5();

			/**
			 * @return A pair of the string and MD5 hash (string is first, MD5 is second).
			 * If you want both this method is more efficient than calling toString() and toMD5() separately
			 */
			std::pair<std::string, std::string> toStringAndMD5();
		};

		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and shouldn't be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLClientHelloMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		~SSLClientHelloMessage() override = default;

		/**
		 * @return A struct containing common fields for client-hello and server-hello messages. Notice this points
		 * directly to the data, so every change will change the actual packet data
		 */
		ssl_tls_client_server_hello* getClientHelloHeader() const
		{
			return reinterpret_cast<ssl_tls_client_server_hello*>(m_Data);
		}

		/**
		 * @return Handshake SSL/TLS version (notice it may be different than SSLLayer#getRecordVersion(). Each
		 * client-hello or server-hello message has both record version and handshake version and they may differ from
		 * one another)
		 */
		SSLVersion getHandshakeVersion() const;

		/**
		 * @return Session ID length in bytes. If server-hello message doesn't include session ID 0 will be returned
		 */
		uint8_t getSessionIDLength() const;

		/**
		 * @return Session ID as byte array. If server-hello message doesn't include session ID nullptr will be returned
		 */
		uint8_t* getSessionID() const;

		/**
		 * @return The number of cipher-suites included in this message
		 */
		int getCipherSuiteCount() const;

		/**
		 * Get a pointer to a cipher-suite by index. The cipher-suites are numbered according to their order of
		 * appearance in the message. If index is out of bounds (less than 0 or larger than total amount of cipher
		 * suites) nullptr will be returned. nullptr will also be returned if the cipher-suite ID is unknown. If you
		 * still want to get the cipher-suite ID you can use getCipherSuiteID()
		 * @param[in] index The index of the cipher-suite to return
		 * @return The pointer to the cipher-suite object or nullptr if index is out of bounds
		 */
		SSLCipherSuite* getCipherSuite(int index) const;

		/**
		 * Get the cipher-suite ID by index. This method just parses the ID from the client-hello message and returns
		 * it. To get more information on the cipher-suite you can use the getCipherSuite() method
		 * @param[in] index The index of the cipher-suite to return
		 * @param[out] isValid Set to "true" if parsing succeeded and the return value is valid or "false" if:
		 * (1) the index is out-of-bounds (less than 0 or larger than total amount of cipher suites) or (2) the parsing
		 * failed. If the value is "false" the return value can be ignored
		 * @return The cipher-suite ID if "isValid" is set to "true". If "isValid" is set to "false" the return value
		 * can be ignored
		 */
		uint16_t getCipherSuiteID(int index, bool& isValid) const;

		/**
		 * @return The value of the compression method byte
		 */
		uint8_t getCompressionMethodsValue() const;

		/**
		 * @return The number of extensions in this message
		 */
		int getExtensionCount() const;

		/**
		 * @return The size (in bytes) of all extensions data in this message. Extracted from the "extensions length"
		 * field
		 */
		uint16_t getExtensionsLength() const;

		/**
		 * Get a pointer to an extension by index. The extensions are numbered according to their order of appearance
		 * in the message. If index is out of bounds (less than 0 or larger than total amount of extensions) nullptr
		 * will be returned
		 * @param[in] index The index of the extension to return
		 * @return The pointer to the extension or nullptr if index is out of bounds
		 */
		SSLExtension* getExtension(int index) const;

		/**
		 * Get a pointer to an extension by numeric type field. Every extension has a 2-byte numeric value representing
		 * its type (for example: renegotiation info extension type is 0x1ff). This method gets the type and returns a
		 * pointer to the extension object
		 * @param[in] type The 2-byte numeric type of the extension
		 * @return A pointer to the extension object of nullptr if this type doesn't exist in this message
		 */
		SSLExtension* getExtensionOfType(uint16_t type) const;

		/**
		 * Get a pointer to an extension by its enum type
		 * @param[in] type The type of extension to return
		 * @return A pointer to the extension object or nullptr if this type doesn't exist in this message
		 */
		SSLExtension* getExtensionOfType(SSLExtensionType type) const;

		/**
		 * Get a pointer to an extension by its class type. This is a templated method that is used with the type of the
		 * requested extension and returns the first extension instance of this type
		 * @return A pointer to the extension object or nullptr if this extension type doesn't exist in this message
		 *
		 */
		template <class TExtension> TExtension* getExtensionOfType() const;

		/**
		 * TLS fingerprinting is a way to identify client applications using the details in the TLS Client Hello packet.
		 * It was initially introduced by Lee Brotherston in his 2015 research:
		 * <https://blog.squarelemon.com/tls-fingerprinting/> This implementation of TLS fingerprint is a C++ version of
		 * Salesforce's JA3 open source project (originally written in Python and Zeek):
		 * <https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967>
		 * @return A SSLClientHelloMessage#ClientHelloTLSFingerprint struct that contains all the elements needed for
		 * creating a TLS fingerprint out of this Client Hello message. This struct has also methods to extract the TLS
		 * fingerprint itself in a string or MD5 formats
		 */
		ClientHelloTLSFingerprint generateTLSFingerprint() const;

		// implement abstract methods

		std::string toString() const override;

	private:
		PointerVector<SSLExtension> m_ExtensionList;
	};

	/**
	 * @class SSLServerHelloMessage
	 * Represents SSL/TLS server-hello message (type 2). Inherits from SSLHandshakeMessage and adds parsing of all
	 * fields of this message including the message extensions, cipher-suite, etc.
	 */
	class SSLServerHelloMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * @struct ServerHelloTLSFingerprint
		 * A struct that contains all the elements needed for creating a Server Hello TLS fingerprinting:
		 * TLS version, Cipher Suite ID, and a list of Extensions IDs.
		 * You can read more about this in SSLServerHelloMessage#generateTLSFingerprint().
		 * This struct contains methods to extract the TLS fingerprint itself: toString() and toMD5()
		 */
		struct ServerHelloTLSFingerprint
		{
			/** TLS version */
			uint16_t tlsVersion;
			/** Cipher Suite ID */
			uint16_t cipherSuite;
			/** A list of extension IDs */
			std::vector<uint16_t> extensions;

			/**
			 * @return A string representing the TLS fingerprint, for example: <b>771,49195,65281-16-11</b>
			 *
			 * This string has the following format: <b>TLSVersion,Cipher,Extensions</b>
			 *
			 * The extension ID are separated with a "-"
			 */
			std::string toString();

			/**
			 * @return An MD5 hash of the string generated by toString()
			 */
			std::string toMD5();

			/**
			 * @return A pair of the string and MD5 hash (string is first, MD5 is second).
			 * If you want both this method is more efficient than calling toString() and toMD5() separately
			 */
			std::pair<std::string, std::string> toStringAndMD5();
		};

		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and shouldn't be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLServerHelloMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		~SSLServerHelloMessage() override = default;

		/**
		 * @return A struct containing common fields for client-hello and server-hello messages. Notice this points
		 * directly to the data, so every change will change the actual packet data
		 */
		ssl_tls_client_server_hello* getServerHelloHeader() const
		{
			return reinterpret_cast<ssl_tls_client_server_hello*>(m_Data);
		}

		/**
		 * @return Handshake SSL/TLS version (notice it may be different than SSLLayer#getRecordVersion(). Each
		 * client-hello or server-hello message has both record version and handshake version and they may differ from
		 * one another).
		 *
		 * <b>NOTE:</b> for TLS 1.3 the handshake version written in ssl_tls_client_server_hello::handshakeVersion is
		 * still TLS 1.2, so a special check is made here see if a SupportedVersions extension exists and if so extract
		 * the version from it. This is the most straight-forward way to detect TLS 1.3.
		 */
		SSLVersion getHandshakeVersion() const;

		/**
		 * @return Session ID length in bytes. If server-hello message doesn't include session ID 0 will be returned
		 */
		uint8_t getSessionIDLength() const;

		/**
		 * @return Session ID as byte array. If server-hello message doesn't include session ID nullptr will be returned
		 */
		uint8_t* getSessionID() const;

		/**
		 * @return A pointer to the cipher suite encapsulated in this message (server-hello message contains one
		 * cipher-suite, the one that will be used to for encryption between client and server). May return nullptr
		 * if the parsing of the message failed or the cipher-suite ID is unknown. If you still want to get the
		 * cipher-suite ID you can use the getCipherSuiteID() method
		 */
		SSLCipherSuite* getCipherSuite() const;

		/**
		 * Get the cipher-suite ID. This method just parses the ID from the server-hello message and returns it.
		 * To get more information on the cipher-suite you can use the getCipherSuite() method
		 * @param[out] isValid Set to "true" if parsing succeeded and the return value is valid or "false" otherwise.
		 * If the value is "false" the return value can be ignored
		 * @return The cipher-suite ID if "isValid" is set to "true". If "isValid" is set to "false" the return value
		 * can be ignored
		 */
		uint16_t getCipherSuiteID(bool& isValid) const;

		/**
		 * @return The value of the compression method byte
		 */
		uint8_t getCompressionMethodsValue() const;

		/**
		 * @return The number of extensions in this message
		 */
		int getExtensionCount() const;

		/**
		 * @return The size (in bytes) of all extensions data in this message. Extracted from the "extensions length"
		 * field
		 */
		uint16_t getExtensionsLength() const;

		/**
		 * Get a pointer to an extension by index. The extensions are numbered according to their order of appearance
		 * in the message. If index is out of bounds (less than 0 or larger than total amount of extensions) nullptr
		 * will be returned
		 * @param[in] index The index of the extension to return
		 * @return The pointer to the extension or nullptr if index is out of bounds
		 */
		SSLExtension* getExtension(int index) const;

		/**
		 * Get a pointer to an extension by numeric type field. Every extension has a 2-byte numeric value representing
		 * its type (for example: renegotiation info extension type is 0x1ff). This method gets the type and returns a
		 * pointer to the extension object
		 * @param[in] type The 2-byte numeric type of the extension
		 * @return A pointer to the extension object of nullptr if this type doesn't exist in this message
		 */
		SSLExtension* getExtensionOfType(uint16_t type) const;

		/**
		 * Get a pointer to an extension by its enum type
		 * @param[in] type The type of extension to return
		 * @return A pointer to the extension object or nullptr if this type doesn't exist in this message
		 */
		SSLExtension* getExtensionOfType(SSLExtensionType type) const;

		/**
		 * Get a pointer to an extension by its class type. This is a templated method that is used with the type of the
		 * requested extension and returns the first extension instance of this type
		 * @return A pointer to the extension object or nullptr if this extension type doesn't exist in this message
		 *
		 */
		template <class TExtension> TExtension* getExtensionOfType() const;

		/**
		 * ServerHello TLS fingerprinting is a way to fingerprint TLS Server Hello messages. In conjunction with
		 * ClientHello TLS fingerprinting it can assist in identifying specific client-server communication (for
		 * example: a malware connecting to its backend server).
		 * ServerHello TLS fingerprinting was introduced in Salesforce's JA3S open source project:
		 * <https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967>
		 * This implementation is a C++ version of Salesforce's JAS3 (originally written in Python and Zeek)
		 * @return A SSLServerHelloMessage#ServerHelloTLSFingerprint struct that contains all the elements needed for
		 * creating a TLS fingerprint out of this Server Hello message. This struct has also methods to extract the TLS
		 * fingerprint itself in a string or MD5 formats
		 */
		ServerHelloTLSFingerprint generateTLSFingerprint() const;

		// implement abstract methods

		std::string toString() const override;

	private:
		PointerVector<SSLExtension> m_ExtensionList;
	};

	/**
	 * @class SSLCertificateMessage
	 * Represents SSL/TLS certificate message (type 11). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as extracting the certificates data. Notice that in most cases this message is spread over
	 * more than 1 packet as its size is too big for a single packet. So SSLCertificateMessage instance will be created
	 * just for the first part of the message - the one encapsulated in the first packet. Other parts (encapsulated in
	 * the following packets) won't be recognized as SSLCertificateMessage messages
	 */
	class SSLCertificateMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLCertificateMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		~SSLCertificateMessage() override = default;

		/**
		 * @return The number of certificates encapsulated in this message (as written in the 'length' field of the
		 * message). Notice that because the message may spread over several packets, not all certificates will
		 * necessarily be in this packet. So, for example, there may be a case where this method return 3 (message
		 * contains 3 certificates) but this message actually contains only 1 certificate as the other 2 are spread over
		 * the other packets
		 */
		int getNumOfCertificates() const;

		/**
		 * Get a certificate by index
		 * @param[in] index The index of the certificate to retrieve
		 * @return A pointer to the certificate object. Notice that if index < 0 or index > num of certificates
		 * encapsulated in current packet a nullptr value will be returned
		 */
		SSLx509Certificate* getCertificate(int index) const;

		// implement abstract methods

		std::string toString() const override;

	private:
		PointerVector<SSLx509Certificate> m_CertificateList;
	};

	/**
	 * @class SSLHelloRequestMessage
	 * Represents SSL/TLS hello-request message (type 0). This message has no additional payload except for the common
	 * payload described in SSLHandshakeMessage
	 */
	class SSLHelloRequestMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLHelloRequestMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLHelloRequestMessage() override = default;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLServerKeyExchangeMessage
	 * Represents SSL/TLS server-key-exchange message (type 12). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as getting the server key exchange params as raw data (parsing of this may be added in the
	 * future)
	 */
	class SSLServerKeyExchangeMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLServerKeyExchangeMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLServerKeyExchangeMessage() override = default;

		/**
		 * @return A pointer to the raw data of the server key exchange params. Currently this data can only returned as
		 * raw, parsing may be added in the future. Notice that if the message is spread over more than 1 packet in a
		 * way params doesn't exist in the first packet, nullptr will be returned
		 */
		uint8_t* getServerKeyExchangeParams() const;

		/**
		 * @return The size of the params field. Notice that if the message is spread over more than 1 packet in a way
		 * the ssl_tls_handshake_layer cannot be parsed from the packet, 0 will be returned. Also, if only part of the
		 * params exist in current packet (and the rest are on consequent packets), the size that will be returned is
		 * the size of the part that exists in the current packet (and not total size of params)
		 */
		size_t getServerKeyExchangeParamsLength() const;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLClientKeyExchangeMessage
	 * Represents SSL/TLS client-key-exchange message (type 16). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as getting the server key exchange params as raw data (parsing of this may be added in the
	 * future)
	 */
	class SSLClientKeyExchangeMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLClientKeyExchangeMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLClientKeyExchangeMessage() override = default;

		/**
		 * @return A pointer to the raw data of the server key exchange params. Currently this data can only be returned
		 * as raw, parsing may be added in the future. Notice that if the message is spread over more than 1 packet in
		 * a way params doesn't exist in the first packet, nullptr will be returned
		 */
		uint8_t* getClientKeyExchangeParams() const;

		/**
		 * @return The size of the params field. Notice that if the message is spread over more than 1 packet in a way
		 * the ssl_tls_handshake_layer cannot be parsed from the packet, 0 will be returned. Also, if only part of the
		 * params exist in current packet (and the rest are on consequent packets), the size that will be returned is
		 * the size of the part that exists in the current packet (and not the total size of params)
		 */
		size_t getClientKeyExchangeParamsLength() const;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLCertificateRequestMessage
	 * Represents SSL/TLS certificate-request message (type 13). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as retrieving client certificate types and authority data
	 */
	class SSLCertificateRequestMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLCertificateRequestMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container);

		~SSLCertificateRequestMessage() override = default;

		/**
		 * @return A reference to a vector containing all client certificate types exist in this message
		 */
		std::vector<SSLClientCertificateType>& getCertificateTypes();

		/**
		 * @return A pointer to the certificate authority data as raw data (byte array). Parsing of this data may be
		 * added in the future. Notice that if this message is spread over several packets in a way none of the
		 * certificate authority data exists in this packet, nullptr will be returned
		 */
		uint8_t* getCertificateAuthorityData() const;

		/**
		 * @return The length of certificate authority data returned by getCertificateAuthorityData(). Notice that if
		 * this message is spread over several packets in a way none of certificate authority data exists in the current
		 * packet, 0 will be returned. Also, if some of the data exists in the consequent packets, the length that will
		 * be returned is the length of data exists in the current packet only (and not the total length)
		 */
		size_t getCertificateAuthorityLength() const;

		// implement abstract methods

		std::string toString() const override;

	private:
		std::vector<SSLClientCertificateType> m_ClientCertificateTypes;
	};

	/**
	 * @class SSLServerHelloDoneMessage
	 * Represents SSL/TLS server-hello-done message (type 14). This message has no additional payload except for the
	 * common payload described in SSLHandshakeMessage
	 */
	class SSLServerHelloDoneMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLServerHelloDoneMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLServerHelloDoneMessage() override = default;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLCertificateVerifyMessage
	 * Represents SSL/TLS certificate-verify message (type 15). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as retrieving signed hash data as raw data (parsing may be added in the future)
	 * @todo This message type wasn't tested in unit-tests
	 */
	class SSLCertificateVerifyMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLCertificateVerifyMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLCertificateVerifyMessage() override = default;

		/**
		 * @return A pointer to the signed hash data as raw data (byte array). Parsing of this data may be added
		 * in the future. Notice that if this message is spread over several packets in a way none of the signed hash
		 * data exists in this packet, nullptr will be returned
		 */
		uint8_t* getSignedHash() const;

		/**
		 * @return The length of signed hash data returned by getSignedHash(). Notice that if this message is spread
		 * over several packets in a way none of this data exists in the current packet, 0 will be returned. Also, if
		 * some of the data exists in the consequent packets, the length that will be returned will be the length of
		 * data exists in the current packet only (and not the total length)
		 */
		size_t getSignedHashLength() const;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLFinishedMessage
	 * Represents SSL/TLS finished message (type 20). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as retrieving signed hash data as raw data (parsing may be added in the future)
	 * @todo This message type wasn't tested in unit-tests
	 */
	class SSLFinishedMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLFinishedMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLFinishedMessage() override = default;

		/**
		 * @return A pointer to the signed hash data as raw data (byte array). Parsing of this data may be added
		 * in the future. Notice that if this message is spread over several packets in a way none of the signed hash
		 * data exists in this packet, nullptr will be returned
		 */
		uint8_t* getSignedHash() const;

		/**
		 * @return The length of signed hash data returned by getSignedHash(). Notice that if the message is spread over
		 * several packets in a way none of this data exists in the current packet, 0 will be returned. Also, if some of
		 * the data exists in the consequent packets, the length that will be returned will be the length of data exists
		 * in the current packet only (and not the total length)
		 */
		size_t getSignedHashLength() const;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLNewSessionTicketMessage
	 * Represents SSL/TLS new-session-ticket message (type 4). Inherits from SSLHandshakeMessage and adds parsing
	 * functionality such as retrieving session ticket data as raw data (parsing may be added in the future)
	 */
	class SSLNewSessionTicketMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLNewSessionTicketMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLNewSessionTicketMessage() override = default;

		/**
		 * @return A pointer to the session ticket data as raw data (byte array). Parsing of this data may be added
		 * in the future. Notice that if this message is spread over several packets in a way none of the signed hash
		 * data exists in current packet, nullptr will be returned
		 */
		uint8_t* getSessionTicketData() const;

		/**
		 * @return The length of session ticket data returned by getSessionTicketData(). Notice that if this message is
		 * spread over several packets in a way none of this data exists in the current packet, 0 will be returned.
		 * Also, if some of the data exist in the consequent packets, the length that will be returned will be the
		 * length of the data existing in the current packet only (and not the total length)
		 */
		size_t getSessionTicketDataLength() const;

		// implement abstract methods

		std::string toString() const override;
	};

	/**
	 * @class SSLUnknownMessage
	 * Represents an unknown type of message or an encrypted message that PcapPlusPlus can't determine its type. In
	 * these cases length can't always be determined from the message itself (especially if the message is encrypted),
	 * so the length of this message will always be the size counted from message start until the end of the layer
	 */
	class SSLUnknownMessage : public SSLHandshakeMessage
	{
	public:
		/**
		 * C'tor for this class. Currently only in use in SSLHandshakeMessage::createHandshakeMessage() and should be
		 * used by a user
		 * @param[in] data The message as raw data
		 * @param[in] dataLen Message raw data length in bytes
		 * @param[in] container The SSL handshake layer which shall contain this message
		 */
		SSLUnknownMessage(uint8_t* data, size_t dataLen, SSLHandshakeLayer* container)
		    : SSLHandshakeMessage(data, dataLen, container)
		{}

		~SSLUnknownMessage() override = default;

		// implement virtual and abstract methods

		/**
		 * @return Always ::SSL_HANDSHAKE_UNKNOWN (overridden from SSLHandshakeMessage)
		 */
		SSLHandshakeType getHandshakeType() const override;

		/**
		 * @return The length of the data from message start until the end of the layer. Since it's an unknown type
		 * or an encrypted message the length parsed from the message can't be guaranteed to be the correct length.
		 * That's why the length returned is the size until the end of the layer
		 */
		size_t getMessageLength() const override;

		std::string toString() const override;
	};

	template <class TExtension> TExtension* SSLClientHelloMessage::getExtensionOfType() const
	{
		size_t vecSize = m_ExtensionList.size();
		for (size_t i = 0; i < vecSize; i++)
		{
			SSLExtension* curElem = const_cast<SSLExtension*>(m_ExtensionList.at(i));
			if (dynamic_cast<TExtension*>(curElem) != nullptr)
				return static_cast<TExtension*>(curElem);
		}

		return nullptr;
	}

	template <class TExtension> TExtension* SSLServerHelloMessage::getExtensionOfType() const
	{
		size_t vecSize = m_ExtensionList.size();
		for (size_t i = 0; i < vecSize; i++)
		{
			SSLExtension* curElem = const_cast<SSLExtension*>(m_ExtensionList.at(i));
			if (dynamic_cast<TExtension*>(curElem) != nullptr)
				return static_cast<TExtension*>(curElem);
		}

		return nullptr;
	}

}  // namespace pcpp
